# -*- coding: utf-8 -*-
import decimal
import datetime
import urllib2
import urllib
import flask
import logging
import numbers
import itertools
import calendar
import json

from google.appengine.ext import db
from google.appengine.ext.db import polymodel
from google.appengine.api import mail
from google.appengine.api import memcache

from flaskext import wtf
import payson
import gviz_api
from celestra import app

import countries
import products


class DecimalProperty(db.Property):

    data_type = str

    def get_value_for_datastore(self, model_instance):
        return str(super(DecimalProperty, self)\
                   .get_value_for_datastore(model_instance))

    def make_value_from_datastore(self, value):
        return decimal.Decimal(value)

    def validate(self, value):
        value = super(DecimalProperty, self).validate(value)
        if value is None or isinstance(value, decimal.Decimal):
            return value
        elif isinstance(value, (basestring, numbers.Integral)):
            return decimal.Decimal(value)
        raise db.BadValueError("Property %s must be convertable to Decimal."
                                % self.name)

    def empty(self, value):
        return value is None


class Player(db.Model):
    firstname = db.StringProperty(required=True)
    lastname = db.StringProperty(required=True)
    email = db.EmailProperty(required=True)
    phone = db.PhoneNumberProperty()
    city = db.StringProperty()
    country = db.StringProperty(required=True)
    birthdate = db.DateProperty(required=True)
#    gender = db.StringProperty()
    height = db.IntegerProperty(required=True)
    size = db.StringProperty(required=True, choices=['XXS',
                                                     'XS',
                                                     'S',
                                                     'M',
                                                     'L',
                                                     'XL',
                                                     'XXL'])
    bsg_pre_knowledge = db.StringProperty(choices=['',
                                                   'None',
                                                   'Limited',
                                                   'Expert',
                                                   'Cylon'])
#    languages = db.StringListProperty()
    signup_time = db.DateTimeProperty(required=True, auto_now_add=True)
    larp_experience = db.BooleanProperty()
    diet = db.StringProperty()
    allergies = db.StringProperty()
    # discountTrue
    # donated


class Product(db.Model):
    description = db.StringProperty(required=True, multiline=True)
    price = DecimalProperty(required=True)
    vat = DecimalProperty(required=True)
    units_available = db.IntegerProperty(required=True)
#    game_key = db.StringProperty()

    @property
    def remaining(self):
        return max(0, self.units_available - \
            len([item for item in self.amount_sold
                 if item.order.accepted and not item.order.revoked]))


def get_available_cc():
    mckey = 'available_cc'
    cached = memcache.get(mckey)  # @UndefinedVariable
    if cached:
        return cached

    game_keys = ('g1', 'g2', 'g3', 'g4')

    def key(v):
        return v.key().name()[:2]

    products = sorted(Product.get_by_key_name([g + cc for g, cc in
                                               itertools.product(game_keys,
                                                                 "abc")]),
                      key=key)

    d = {}
    for g, cc in itertools.groupby(products, key=key):
        d[g] = [(c.key().name(), c.remaining)
                for c in sorted(cc, key=lambda k: k.key().name())]
    memcache.set(mckey, d)  # @UndefinedVariable
    return d


chart_labels = {'g1': 'Mar 1-3',
                'g2': 'Mar 8-10',
                'g3': 'Mar 15-17',
                'g4': 'Mar 22-24',
                }

tickets_available_table = gviz_api.DataTable([('game', 'string', 'Game'),
                                              ('A', 'string', 'Character Class A'),
                                              ('B', 'string', 'Character Class B'),
                                              ('C', 'string', 'Character Class C')])


def get_game_stats_data_table():
    data = [(chart_labels[g], a[0][1], a[1][1], a[2][1]) for g, a in
             get_available_cc.iteritems()]
    tickets_available_table.LoadData(data)
    return tickets_available_table


class Order(db.Model):
    invoice_no = db.IntegerProperty()
    player = db.ReferenceProperty(required=True,
                                  collection_name='orders')
    creation_time = db.DateTimeProperty(required=True,
                                        auto_now_add=True)
    total = DecimalProperty()
    remainder = DecimalProperty()
    revoked = db.BooleanProperty(default=False)
    character_comments = db.TextProperty()
    coplayer_comments = db.TextProperty()
    other_comments = db.TextProperty()
    discount = DecimalProperty()
    accepted = db.BooleanProperty(default=False)

    @property
    def vat_total(self):
        return sum(i.price * i.product.vat for i in self.items)

    @property
    def first_interval_days(self):
        return 5 if self.player.country == 'SE' else 7

    def update_accepted(self, now):
        days = self.first_interval_days
        accepted = ((self.total - self.remainder) >= 1000 or
                    (now - datetime.timedelta(days=days) <
                     self.creation_time))
        if accepted != self.accepted:
            self.accepted = accepted
            return True
        return False


def check_accepted_orders():
    now = datetime.datetime.utcnow()
    db.put([order for order in Order.all() if
            order.update_accepted(now)])


class OrderItem(db.Model):
    order = db.ReferenceProperty(Order,
                                 required=True,
                                 collection_name='items')
    product = db.ReferenceProperty(Product,
                                   required=True,
                                   collection_name='amount_sold')
    price = DecimalProperty(required=True)


class Payment(polymodel.PolyModel):
    order = db.ReferenceProperty(required=True,
                                 collection_name='payments')
    amount = DecimalProperty(required=True)
    timestamp = db.DateTimeProperty(required=True, auto_now_add=True)


class PaysonPayment(Payment):
    token = db.StringProperty()
    status = db.StringProperty()
    purchase_id = db.StringProperty()

    @classmethod
    def get_from_token(cls, args):
        token = args.get('TOKEN', args.get('token', ''))
        q = cls.all()
        q.filter('token =', token)
        return q.get()

    def update(self, paydetails):
        if self.order.player.email == paydetails.senderEmail:
            self.purchase_id = paydetails.purchaseId
            self.status = paydetails.status
            if self.status == 'COMPLETED':
                self.order.remainder = self.order.remainder - self.amount
            db.put([self, self.order])
            logging.info('Updated payment from {email} to {status} for {amount} kr'\
                         .format(email=paydetails.senderEmail,
                                 status=paydetails.status,
                                 amount=self.amount))
        else:
            logging.error('Email mismatch for %s'
                  % str(self.key()))

    @property
    def description(self):
        return u'Direct payment through Payson (%s)' % self.purchase_id


class CrowdfundingPayment(Payment):
    description = u'Contribution through crowdfunding'
    status = 'COMPLETED'


class InvoicePayment(Payment):
    description = u'Invoice payment'
    status = db.StringProperty(default=u'CREATED')


class MultiCheckboxField(wtf.SelectMultipleField):
    """
    A multiple-select, except displays a list of checkboxes.

    Iterating the field will produce subfields, allowing custom rendering of
    the enclosed checkbox fields.
    """
    widget = wtf.widgets.ListWidget(prefix_label=False)
    option_widget = wtf.widgets.CheckboxInput()


def check_products_remaining(form, field):
    p = Product.get_by_key_name(field.data)
    if not p:
        raise wtf.ValidationError('No character class selected.')
    if p.remaining <= 0:
        raise wtf.ValidationError('No products left of %s' % p.description)


def check_double_signup(form, field):
    q = Player.all()
    email = form.email.data.lower()
    q.filter('email =', email)
    player = q.get()
    if player:
        for o in player.orders:
            if o.accepted:
                for i in o.items:
                    if i.product.key().name() == field.data:
                        raise wtf.ValidationError(
                                              "Can't sign up to same game twice.")


def validate_birthdate(form, field):
    try:
        birthdate = form.get_birthdate()
    except (ValueError, TypeError):
        raise wtf.ValidationError('Invalid birthdate')
    if form.game.data == 'g1':
        game_date = datetime.date(1995, 3, 1)
    elif form.game.data == 'g2':
        game_date = datetime.date(1995, 3, 8)
    elif form.game.data == 'g3':
        game_date = datetime.date(1995, 3, 15)
    elif form.game.data == 'g4':
        game_date = datetime.date(1995, 3, 22)
    else:
        return
    if  birthdate > game_date:
        raise wtf.ValidationError(
              u'You have to be at least 18 years of age, sorry.')


class SignUpForm(wtf.Form):
    # TODO: Add label + description properly, add price from product data
    game = wtf.RadioField(u'Show',
                           choices=[('g1', u'March 1-3;'),
                                    ('g2', u'March 8-10;English language only'),
                                    ('g3', u'March 15-17'),
                                    ('g4', u'March 22-24;Registration not yet opened')],
                          validators=[wtf.Optional(), ])
    character_class = wtf.RadioField('Character class',
                                     validators=[wtf.Required(),
                                                 check_products_remaining,
                                                 check_double_signup],
                                     choices=[(p.key, p.description) for p in
                                              products.game_choices])

    firstname = wtf.TextField(u'First name', validators=[wtf.Required(), ])
    lastname = wtf.TextField(u'Last name', validators=[wtf.Required(), ])
    email = wtf.html5.EmailField(u'E-mail', validators=[wtf.Required(),
                                                        wtf.Email(), ])
    phone = wtf.html5.TelField(u'Phone')
    city = wtf.TextField(u'City')
    country = wtf.SelectField(u'Country',
                              validators=[wtf.Required(), ],
                              choices=countries.all,
                              default='SE')
#    birthdate = wtf.DateField(u'Day of birth (YYYY-mm-dd)')
    dob_year = wtf.SelectField(choices=list((i, str(i)) for i in xrange(1995, 1912, -1)),
                               coerce=int,
                               validators=[wtf.Required(),
                                           validate_birthdate])
    dob_month = wtf.SelectField(choices=list((i + 1, m) for i, m in enumerate(calendar.month_name[1:])),
                                coerce=int,
                                validators=[wtf.Required()])
    dob_day = wtf.SelectField(choices=list((i, str(i)) for i in xrange(1, 32)),
                              coerce=int,
                              validators=[wtf.Required()])

    height = wtf.IntegerField(u'Height, in cm', validators=[wtf.Required(), ])
    size = wtf.SelectField(u'Clothes size (mens sizes)',
                           choices=zip([''] + Player.size.choices,
                                       ['Size'] + Player.size.choices),
                           validators=[wtf.Required(), ],
                           default='',
                           description=u'Most of the costumes we order are listed with men\'s sizes only')
#    languages = MultiCheckboxField(u'Language(s)',
#                                   choices=[('en', u'English'),
#                                            ('sv', u'Swedish'),
#                                            ('')])
    bsg_pre_knowledge = wtf.SelectField(u'BSG Knowledge',
                                    choices=zip(Player.bsg_pre_knowledge.choices,
                                                itertools.chain(['BSG knowledge...'], Player.bsg_pre_knowledge.choices[1:])),
                                        default='')
    pre_larp_experience = wtf.BooleanField(u'Previous Larp experience',
                           description=u'Are you completely new to larping? No problem!')

    character_comments = wtf.TextAreaField(u'Character wishes')
    coplayer_comments = wtf.TextAreaField(u'Co-player wishes')
    other_comments = wtf.TextAreaField(u'Other comments')

    diet = wtf.StringField(u'Dietary restrictions')
    allergies = wtf.StringField(u'Allergies or other medical conditions that we need to be aware of')

    bed_thur_friday = wtf.BooleanField(u'Hostel Thursday-Friday, + 150 SEK.',)
#                                       validators=[check_products_remaining, ])
    bed_sun_monday = wtf.BooleanField(u'Hostel Sunday-Monday, + 150 SEK',)
#                                       validators=[check_products_remaining, ])
    breakfasts = wtf.BooleanField(u'Breakfast at hostel for extra nights, '
                                  u'SEK 35')
    tshirt = wtf.BooleanField(u'Unique Celestra souvenir T-shirt, 200 SEK')
    share_to_facebook = wtf.BooleanField(u'Share to Facebook', default=True)
    toc = wtf.BooleanField(u'Terms and Conditions', default=True)
    message = wtf.TextField(validators=[wtf.Optional(), ])  # this is a fake to stop spam

    def validate_toc(self, field):
        if not field.data:
            raise wtf.ValidationError(u"You can't register without accepting the terms and conditions")

    def get_birthdate(self):
        return datetime.date(self.dob_year.data,
                             self.dob_month.data,
                             self.dob_day.data)

    def save(self):
        import interested
        # first try find player by email, if found update
        player = self.find_or_create_player()

        q = player.orders
        q.filter('accepted', True)
        q.filter('revoked IN', (False, None))
        if q.count() == 0:  # discount and donation amount spent after one use
            donated, discount, tshirt = interested.get_donated_and_discount(player.email)
        else:
            donated, discount, tshirt = (0, 0, False)

        if self.game.data:
            return self.create_order(player, donated, discount, tshirt)
        else:
            flask.flash('Personal info updated.')
            return flask.render_template('signup.html',
                                         form=self,
                                         action='/signup/')

    def find_or_create_player(self):
        q = Player.all()
        email = self.email.data.lower()
        q.filter('email =', email)
        player = q.get()
        birthdate = self.get_birthdate()
        if not player:
            player = Player(firstname=self.firstname.data,
                            lastname=self.lastname.data,
                            email=email,
                            city=self.city.data,
                            country=self.country.data,
                            birthdate=birthdate,
                            height=self.height.data,
                            size=self.size.data,
                            bsg_pre_knowledge=self.bsg_pre_knowledge.data,
#                            languages=self.languages.data,
                            larp_experience=self.pre_larp_experience.data,
                            diet=self.diet.data,
                            allergies=self.allergies.data)
            if self.phone.data:
                player.phone = self.phone.data
            player.put()
            logging.info('Created player %s %s (%s)' % (player.firstname,
                                                        player.lastname,
                                                        player.email))
        else:
            player.firstname = self.firstname.data
            player.lastname = self.lastname.data
            if self.city.data:
                player.city = self.city.data
            player.country = self.country.data
            player.birthdate = birthdate
            player.height = self.height.data
            player.size = self.size.data
            player.bsg_pre_knowledge = self.bsg_pre_knowledge.data
            player.larp_experience = self.pre_larp_experience.data
            if self.diet.data:
                player.diet = self.diet.data
            if self.allergies.data:
                player.allergies = self.allergies.data
            player.put()
        return player

#    @db.transactional(xg=True)

    def create_cf_payment(self, donated, order):
        if donated > 0:
            import interested
            for date, amount in interested.get_donations(order.player.email):
                cfp = CrowdfundingPayment(parent=order, order=order,
                    amount=amount,
                    timestamp=date)
                cfp.put()
                send_email(order, cfp)

    def create_order(self, player, donated, discount, tshirt):
        order = Order(parent=player,
                      player=player,
                      character_comments=self.character_comments.data,
                      coplayer_comments=self.coplayer_comments.data,
                      other_comments=self.other_comments.data,
                      discount=discount)
        order.put()

        def order_item(pkey, price=None, use_discount=False):
            product = Product.get_by_key_name(pkey)
            if use_discount:
                price = product.price - (product.price * discount)
            elif price is None:
                price = product.price
            return OrderItem(parent=order,
                             order=order,
                             product=product,
                             price=price)

        order_items = [order_item(self.character_class.data,
                                  use_discount=True),
                       order_item(self.game.data + '_food_hostel',
                                  use_discount=True), ]

        total = sum(i.price for i in order_items)
        if donated > total:  # add a bonus product to make receipt for crowdfunders add up
            price = donated - total
            order_items.append(order_item('cfbonus', price=price))

        if self.bed_thur_friday.data:
            order_items.append(order_item(self.game.data +
                                          '_hostel_thursday'))
            if self.breakfasts.data:
                order_items.append(order_item(self.game.data +
                                              '_breakfast_friday'))
        if self.bed_sun_monday.data:
            order_items.append(order_item(self.game.data +
                                          '_hostel_sunday'))
            if self.breakfasts.data:
                order_items.append(order_item(self.game.data +
                                              '_breakfast_monday'))
        if tshirt:
            order_items.append(order_item('tshirt', price=0))
        elif self.tshirt.data:
            order_items.append(order_item('tshirt'))

        total = sum(i.price for i in order_items)
        order.total = total
        order.remainder = order.total - donated
        db.put([order, ] + order_items)

        self.create_cf_payment(donated, order)
        logging.info('Created Order for %s with [%s]' % (order.player.email,
                     ', '.join(i.product.key().name() for i in order_items)))
        return order


def validate_payson_amount(form, field):
    if form.payment_method.data == 'payson' and \
        (not field.data or field.data < 0):
        raise wtf.validators.StopValidation(
            u'Positive amount must be given for card / bank transfer payment')


class PaymentForm(wtf.Form):
    payment_method = wtf.RadioField(u'Payment method',
                                    choices=[('payson', 'Card or Bank Transfer - Secure Internet payments provided by Payson (does not work from US unfortunately)'),
                                    ('invoice', 'Invoice - this option allows you to split total amount in three separate payments')],
                           validators=[wtf.Required(), ])
#    amount = wtf.DecimalField(u'Amount to pay now',
#                              places=2,
#                              rounding=decimal.ROUND_UP,
#                              validators=[validate_payson_amount,
#                                          wtf.Optional(), ])
    order_key = wtf.HiddenField(u'order_key')
    share_to_facebook = wtf.HiddenField(u'share_to_facebook')

    def save(self, payson_api):
        order = Order.get(self.order_key.data)
        order.accepted = True
        order.put()
        flask.flash(messages['registration ok'], 'success')
        amount = order.remainder
        stfb = json.loads(self.share_to_facebook.data)
        if self.payment_method.data == 'payson' and amount:
            payment = PaysonPayment(parent=order,
                                    order=order,
                                    amount=amount)
            payment.put()
            try:
                if app.config.get('TESTING'):
                    host = 'http://localhost:8080'
                else:
                    host = 'https://celestra-larp.appspot.com'
                pay_result = payson_api.pay(
                    returnUrl='%s/payment/return' % host,
                    cancelUrl='%s/payment/cancel' % host,
                    ipnNotificationUrl='%s/payment/ipn' % host,
                    memo=u'Ticket to "The Monitor Celestra"',
                    senderEmail=order.player.email,
                    senderFirstName=order.player.firstname,
                    senderLastName=order.player.lastname,
                    receiverList=[payson.Receiver(email='payments@alternaliv.se',
                                                  amount=amount), ],
                    guaranteeOffered='NO',
                    localeCode='EN',
                    trackingId=str(payment.key()),
                    custom=stfb)
            except urllib2.URLError:
                flask.flash(messages['payson error'], 'warning')
                return payment_page_url(order, stfb)
            else:
                if pay_result.success:
                    payment.token = pay_result.token
                    payment.put()
                    return flask.redirect(pay_result.forward_pay_url)
                else:
                    flask.flash(messages['payson error'], 'warning')
                    return payment_page_url(order, stfb)
        if order.remainder > 0:
            send_email(order)
            logging.info('Sent invoice mail to %s for %s kr.'
                         % (order.player.email,
                            order.remainder))
        if stfb:
            return flask.redirect(share_to_facebook_url)
        else:
            return flask.redirect('/signup/complete/')


fb_data = {'app_id': '393272854084090',
'link': 'http://www.celestra-larp.com/',
'picture': 'https://celestra-larp.appspot.com/static/img/celestra_square.jpg',
'name': u'I enlisted on "The Monitor Celestra"!',
'caption': u'Nordic Live Action Roleplaying inspired by "Battlestar Galactica"',
'description': u'In March 2013 I will attend this epic adventure on board a real cold war era warship in Gothenburg, Sweden. "The Monitor Celestra" is a technology enhanced science-fiction larp about cultural and personal conflict in the shadow of the destruction of the twelve colonies of mankind.',
'redirect_uri': 'https://celestra-larp.appspot.com/signup/complete/'}
fb_qs = urllib.urlencode(fb_data)

share_to_facebook_url = 'https://www.facebook.com/dialog/feed?' + fb_qs


def payment_page_url(order, stfb):
    return '/payment/?okey=%s&stfb=%s' % (str(order.key()),
                                          str(stfb).lower())


def send_email(order, payment=None):
    subject = u"The Monitor Celestra - %s" % \
        u'Receipt' if payment else u'Invoice'
    player = order.player
    b = flask.render_template('receipt_email.html',
                              order=order,
                              payment=payment)
    mail.send_mail(sender=u"The Monitor Celestra Payments Crew <payments@celestra-larp.com>",
                   to="%s %s <%s>" % (player.firstname,
                                      player.lastname,
                                      player.email),
                   subject=subject,
                   body=u"HTML message only",
                   html=b)


messages = {
'payson error': \
u"""There was an error in the communication with our payments provider. Please try later or use another payment method.""",
'registration ok': \
u"""You have booked your ticket to "The Monitor Celestra".""",
'payment ok': \
u"Payment of SEK %d completed.",
'payment error': \
u"""Something went wrong handling your payment. Maybe you canceled or maybe you didn't have any money, or
 maybe something technical went wrong.. Please try later or use another payment method.""",
'registration ok': \
u"""Your ticket to "The Monitor Celestra" is reserved for you.
Remember that you must pay the minimum registration fee, if you haven't done so already, 
within three days to avoid losing your reservation."""
}


class PaymentRegistrationForm(wtf.Form):
    reference = wtf.StringField(u'Referens (e-post)',
                                validators=[wtf.Required(), ])
    amount = wtf.DecimalField(u'Belopp (heltal)',
                              validators=[wtf.Required(), ])
    date = wtf.DateField(u'Datum (ÅÅÅÅ-MM-DD)',
                         validators=[wtf.Required(), ])

    def save(self):
        q = Player.all()
        q.filter('email', self.reference.data.strip())
        player = q.get()
        if player:
            q = player.orders
            q.filter('accepted', True)
            if q.count() > 1:
                flask.flash(u'Flera beställningar finns för epostadressen, hantera på annat sätt', 'warning')
                return
            order = q.get()
            if order:
                if order.remainder <= 0:
                    flask.flash(u'Beställning redan fullbetald.', 'warning')
                    return
                date = datetime.datetime.combine(self.date.data, datetime.time.min)
                payment = InvoicePayment(parent=order,
                                         order=order,
                                         amount=self.amount.data,
                                         timestamp=date,
                                         status='COMPLETED')
                order.remainder = order.remainder - self.amount.data
                db.put([order, payment])
                send_email(order, payment)
                flask.flash(u'Payment registered!', 'success')
                logging.info(u'Registered payment of SEK %s to %s and sent receipt email' %
                             (str(self.amount.data), order.player.email))
            else:
                flask.flash(u'Error in looking up order', 'error')
        else:
            flask.flash(u'Could not find player with email %s' %
                        self.reference.data, 'error')


class OrderEditForm(wtf.Form):
    key = wtf.HiddenField()
    invoice_no = wtf.IntegerField(u'Invoice number',
                                  validators=[wtf.Optional(), ])
    player = wtf.SelectField(u'Player', choices=[('', ''), ])
    cc = wtf.SelectField(u'Game choice',
                         choices=[(p.key, p.description) for p in
                                  products.game_choices],
                         validators=[wtf.Required(), ])
    xtra_nights = wtf.SelectMultipleField(u'Xtra hostel',
                                          choices=[(p.key, p.description) for p
                                                   in products.xtra_hostel])
    breakfasts = wtf.SelectMultipleField(u'Breakfasts',
                                          choices=[(p.key, p.description) for p
                                                   in products.breakfasts])
    tshirt = wtf.BooleanField(u'T-Shirt')
    accepted = wtf.BooleanField(u'Accepted')
    total = wtf.DecimalField(u'Total amount')
    remainder = wtf.DecimalField(u'Remainder')
    revoked = wtf.BooleanField(u'Revoke order')
    character_comments = wtf.TextAreaField(u'Character comments')
    coplayer_comments = wtf.TextAreaField(u'Coplayer comments')
    other_comments = wtf.TextAreaField(u'Other comments')
    discount = wtf.DecimalField(u'Discount')
    send_new_email = wtf.BooleanField(u'Send another invoice',
                                      default=False)

    def populate(self, order):
        self.key.data = str(order.key())
        self.invoice_no.data = order.invoice_no
        self.accepted.data = order.accepted
        self.total.data = order.total
        self.remainder.data = order.remainder
        self.revoked.data = order.revoked
        self.character_comments.data = order.character_comments
        self.coplayer_comments.data = order.coplayer_comments
        self.other_comments.data = order.other_comments
        self.discount.data = order.discount
        self.player.data = str(order.player.key())
        ccp = products.get_cc_product(order)
        self.cc.data = ccp
        self.xtra_nights.data = list(products.get_hostel_products(order))
        self.breakfasts.data = list(products.get_breakfast_products(order))
        self.tshirt.data = products.get_tshirt(order)

    def set_player_choices(self, order):
        pkey = str(order.player.key())
        self.player.choices = [(pkey,
                                ' '.join((order.player.firstname,
                                          order.player.lastname))), ]

    def save(self):
        order = Order.get(self.key.data)
        order.invoice_no = self.invoice_no.data
        order.accepted = self.accepted.data
        order.total = self.total.data
        order.remainder = self.remainder.data
        order.revoked = self.revoked.data
        order.character_comments = self.character_comments.data
        order.coplayer_comments = self.coplayer_comments.data
        order.other_comments = self.other_comments.data
        order.discount = self.discount.data

        # change date, change xtra nights and breakfast
        old_items = [i.product.key().name() for i in order.items]
        db.delete(order.items)
        fh = self.cc.data[:2] + '_food_hostel'
        base = [self.cc.data, fh]
        products = Product.get_by_key_name(base)
        items = []
        for p in products:
            items.append(OrderItem(parent=order,
                                   order=order,
                                   product=p,
                                   price=p.price - (p.price * order.discount)))
        xtra = self.xtra_nights.data + self.breakfasts.data
        if self.tshirt.data:
            xtra.append('tshirt')
        for p in Product.get_by_key_name(xtra):
            items.append(OrderItem(parent=order,
                                   order=order,
                                   product=p,
                                   price=p.price))
        logging.info('Updating order for %s from %r to %r' %
                     (order.player.email, old_items, base + xtra))
        db.put([order, ] + items)

        if self.send_new_email.data:
            send_email(order)
            logging.info('Sent updated invoice data to %s' % order.player.email)

        memcache.delete('available_cc')

